package timingwheel

import (
	"container/list"
	"sync"
	"sync/atomic"
	"time"
)

type (
	// Task 定义定时任务的数据结构
	Task struct {
		id      int64         // 任务唯一ID
		job     func()        // 任务函数
		delay   int64         // 定时任务执行时间戳
		cancel  bool          // 用于标记任务已被取消(惰性删除)
		period  time.Duration // 重复执行间隔(0表示不重复)
		repeats int           // 重复执行次数(-1表示一直重复；0表示不重复；>0表示指定次数)
	}

	// SingleTimeWheel 定义单级时间轮的数据结构
	SingleTimeWheel struct {
		slots           []*list.List  // 槽数组
		slotNumber      int           // 槽的数量
		tickDuration    time.Duration // 每个槽的时间间隔
		currentPosition int           // 当前指针位置
		stopChan        chan struct{} // 停止通道
		taskChan        chan *Task    // 任务队列
		taskMap         sync.Map      // 任务ID映射，用于取消任务
		tidGenerator    atomic.Int64  // 任务ID生成器
		isRunning       atomic.Bool   // 运行状态原子标记

		lock      sync.RWMutex
		waitGroup sync.WaitGroup
	}
)

// NewSingleTimeWheel 新建一个单级时间轮
func NewSingleTimeWheel(tickDuration time.Duration, slotNumber int) *SingleTimeWheel {
	if tickDuration <= 0 {
		panic("timewheel: tick duration must be positive")
	}

	if slotNumber <= 0 {
		panic("timewheel: slot number must be positive")
	}

	wheel := &SingleTimeWheel{
		slots:        make([]*list.List, slotNumber),
		stopChan:     make(chan struct{}),
		taskChan:     make(chan *Task, 1024),
		tickDuration: tickDuration,
		slotNumber:   slotNumber,
	}

	// 初始化槽
	for i := 0; i < slotNumber; i++ {
		wheel.slots[i] = list.New()
	}

	return wheel
}

// Start 启动时间轮
func (own *SingleTimeWheel) Start() {
	// 检查时间轮是否已启动
	if own.isRunning.Swap(true) {
		return
	}

	// 创建时间轮驱动计时器
	ticker := time.NewTicker(own.tickDuration)
	own.waitGroup.Add(1)

	// 异步允许时间轮
	go func() {
		defer own.waitGroup.Done()

		for {
			select {
			case task := <-own.taskChan:
				own.addTaskToSlot(task)
			case <-own.stopChan:
				return
			case <-ticker.C:
				own.tick()
			}
		}
	}()
}

// Stop 停止时间轮
func (own *SingleTimeWheel) Stop() {
	// 检查时间轮是否已停止
	if !own.isRunning.Load() {
		return
	}

	// 停止时间轮
	close(own.stopChan)
	own.waitGroup.Wait()
	own.isRunning.Store(false)
}

// AddTask 添加定时性任务
//
// 注意：重复执行任务的间隔时间 period 需要大于时间轮的最小嘀嗒时间；
// repeats=-1 表示一直定时执行任务
func (own *SingleTimeWheel) AddTask(delay time.Time, job func(), period time.Duration, repeats ...int) int64 {
	task := &Task{
		id:      own.tidGenerator.Add(1),
		job:     job,
		delay:   delay.UnixNano(),
		repeats: 1,
	}

	if period > 0 {
		task.period = period
	}

	if task.period > 0 && len(repeats) > 0 {
		if repeats[0] < 0 {
			task.repeats = repeats[0]
		} else {
			task.repeats += repeats[0]
		}
	}

	// 添加任务到任务队列
	own.taskChan <- task
	return task.id
}

// CancelTask 取消任务
func (own *SingleTimeWheel) CancelTask(tid int64, fn func()) bool {
	if value, loaded := own.taskMap.LoadAndDelete(tid); loaded {
		value.(*Task).cancel = true
		if fn != nil {
			fn()
		}

		return true
	}

	return false
}

// addTaskToSlot 内部添加任务方法
func (own *SingleTimeWheel) addTaskToSlot(task *Task) {
	// 只要 task.repeats == 0，则表明任务执行不需要在执行
	if task.repeats == 0 {
		return
	}

	delay := task.delay - time.Now().UnixNano()
	if delay <= int64(own.tickDuration) {
		own.callback(task)
		return
	}

	// 计算任务应该在多少个时间滴答后执行
	ticks := delay / int64(own.tickDuration)
	// 计算任务应该放入的槽位置 (考虑当前指针位置)
	pos := (own.currentPosition + int(ticks)%own.slotNumber) % own.slotNumber
	// 将任务添加到对应的槽中
	own.slots[pos].PushBack(task)
	// 保存到任务映射
	own.taskMap.Store(task.id, task)
}

// tick 时间轮转动处理
func (own *SingleTimeWheel) tick() {
	now := time.Now().UnixNano()

	own.lock.Lock()

	// 获取当前槽
	currentSlot := own.slots[own.currentPosition]

	// 处理当前槽中的任务
	var next *list.Element
	for e := currentSlot.Front(); e != nil; e = next {
		task := e.Value.(*Task)

		// 任务时间到，执行回调
		if task.delay-now <= int64(own.tickDuration) {
			own.callback(task)
			currentSlot.Remove(e)
			continue
		}

		// 未到期，重新计算位置并放回
		own.addTaskToSlot(task)
	}

	// 移动指针
	own.currentPosition = (own.currentPosition + 1) % own.slotNumber
	own.lock.Unlock()
}

func (own *SingleTimeWheel) callback(task *Task) {
	// 任务取消或重复执行次数结束
	if task.cancel || task.repeats == 0 {
		return
	}

	// 若 task.repeats>0 执行一次任务，则重复次数减1
	if task.repeats > 0 {
		task.repeats--
	}

	// 执行任务
	own.waitGroup.Add(1)
	go func(t *Task) {
		// 执行任务
		t.job()

		// 处理重复任务
		if t.period > 0 && t.repeats != 0 {
			t.delay = time.Now().Add(t.period).UnixNano()
			own.taskChan <- t // 重新添加
		} else {
			own.taskMap.Delete(t.id)
		}

		own.waitGroup.Done()
	}(task)
}
